<?php

namespace System\library\loader;

/**
 * <b>Class</b> :        Loader<br>
 * <b>Fonction</b> :     Permet l'auto-chargement des classes/interface php
 * <br>
 * @version              1.0
 * @author               Steuf
 * @version              2.0<br>
 *                       Ajout de la phpdoc<br>
 *                       Prise en charge des namespace MidichloriansPHP/Controllers/Models
 * <br>
 * @author               Dread <dreadlokeur@gmail.com>
 * @license              Licence publique générale GNU http://www.gnu.org/licenses/gpl.html
 * @copyright            Copyright 2011 - MidichloriansPHP and contributors
 * @package              System
 * @subpackage           library\loader
 *
 * TODO : cache system for vendors class
 */
class ClassLoader {

    /**
     * L'attribut qui permet de stocker le/les repertoires
     *
     * @access private
     * @static
     * @var array
     */
    private static $_directory = array();
    /**
     * L'attribut qui permet de stocker le/les namespace
     *
     * @access private
     * @static
     * @var array
     */
    private static $_namespace = array();

    /**
     * Constructeur, ajoute le répertoire des classes "plugins" et le namespace MidichloriansPHP
     *
     * @access public
     * @param bool $loadVendorsClass load des classes externe (vendors)
     * @return void
     */
    public function __construct($loadVendorsClass = true) {
        if ($loadVendorsClass)
            $this->addDirectory(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendors' . DIRECTORY_SEPARATOR);

        //var_dump(__DIR__.DIRECTORY_SEPARATOR);
        //$this->addNamespacePath('System', __DIR__.DIRECTORY_SEPARATOR);
        //$this->addNamespacePath('System\Libs', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
    }

    /**
     * Permet d'ajouter un namespace à la liste des namespace enregistré
     *
     * @access public
     * @static
     * @param string $name le nom du namespace
     * @param string $path le repertoire
     * @return void
     */
    public static function addNamespacePath($name, $path) {
        if (!is_dir($path))
            throw new \Exception('Le répertoire "' . $path . '" n\'existe pas');
        self::$_namespace[$name] = realpath($path) . DIRECTORY_SEPARATOR;
    }

    /**
     * Permet d'ajouter une valeur au tableau attribut "directory" et de lancer la methode addSubdirectory afin de scan...
     *
     * @access public
     * @static
     * @param string $path le répertoire
     * @return void
     */
    public static function addDirectory($path) {
        if (!is_dir($path))
            throw new \Exception('"' . $path . '" isn\'t a valid directory');
        self::$_directory[md5($path)] = $path;
        self::_addSubDirectory($path);
    }

    /**
     * Permet d'ajouter les sous-repertoire contenu dans un repertoire à l'attribut directory de manière recursive
     *
     * @access private
     * @static
     * @param string $d le répertoire à scanner afin de recup la liste des rep qu'il contient
     * @return void
     */
    private static function _addSubDirectory($d) {
        // Remove all values that not needed
        $files = array_diff(scandir($d), array('.', '..', '.svn'));
        foreach ($files as &$f) {
            $dir = $d . $f . DIRECTORY_SEPARATOR;
            if (is_dir($dir)) {
                self::$_directory[md5($dir)] = $dir;
                self::_addSubDirectory($dir);
            }
        }
    }

    /**
     * Permet attribuer un parrametre à la fonction spl_autoload_register, on lui dit qu'il doit load avec la callback _autoload de cette classe
     *
     * @access public
     * @param void
     * @return void
     */
    public function register() {
        if (!function_exists('spl_autoload_register'))
            throw new \Exception('spl_autoload does not exist in this PHP installation');
        spl_autoload_register(array($this, '_autoload'));
    }

    /**
     * Load class, si pas de namespace on parcourt les directory enregistrés
     *
     * @access private
     * @param string $class la class à charger
     * @return void
     */
    private function _autoload($class) {
        if (class_exists($class, false) || interface_exists($class, false))
            return;
        $nameSpace = $this->getRootNamespace($class);
        if ($nameSpace !== null) {
            if (!key_exists($nameSpace, self::$_namespace))
                return;
            $path = str_replace(array($nameSpace . '\\', '\\'), array(self::$_namespace[$nameSpace], DIRECTORY_SEPARATOR), $class);
            // include class
            $this->_includeFile($path, $class);
        } else {
            // Get all directory for search file
            foreach (self::$_directory as $dir) {
                // Search class file and include
                $this->_includeFile($dir . $class);
            }
        }
    }

    /**
     * Essaye d'inclure une classe, une interface ou un fichier php si celui-ci existe
     *
     * @access private
     * @param string $class la classe à inclure
     * @return <void>
     */
    private function _includeFile($class) {
        // Class
        if (file_exists($class . '.class.php')) {
            require_once $class . '.class.php';
            if (class_exists($class, false) || interface_exists($class, false))
                return;
        }
        // abstract file
        if (file_exists($class . '.abstract.php')) {
            require($class . '.abstract.php');
            if (class_exists($class, false) || interface_exists($class, false))
                return;
        }
        // Interface
        if (file_exists($class . '.interface.php')) {
            require_once $class . '.interface.php';
            if (class_exists($class, false) || interface_exists($class, false))
                return;
        }
        // Default php file
        if (file_exists($class . '.php')) {
            require_once $class . '.php';
            if (class_exists($class, false) || interface_exists($class, false))
                return;
        }
    }

    /**
     * Permet de déterminer le namespace d'une classe
     *
     * @access private
     * @param string $class la classe
     * @return void
     */
    private function getRootNamespace($class) {
        $rootNamespaceLenght = 0;
        if (strpos($class, '\\'))
            $rootNamespaceLenght = strcspn($class, '\\');

        if ($rootNamespaceLenght == 0)
            return null;
        return substr($class, 0, $rootNamespaceLenght);
    }

}

?>
